import React, {
  useEffect, useState, useMemo,
} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { makeStyles } from '@mui/styles';
import { TreeView, TreeItem } from '@mui/lab';
import { ExpandMore, ChevronRight, Close } from '@mui/icons-material/';
import {
  Alert,
  CircularProgress,
  Checkbox,
  Stack,
  IconButton,
  Typography,
} from '@mui/material';
import _ from 'lodash';
import classNames from 'classnames';
import { isolateElements } from 'components/Viewer/viewer-helper';
import { setActiveClassifyingSide } from 'redux/actions/app';
import { getRgbString } from 'functions/colorizing';

function isEventFromIcon(e) {
  const testId = e.target.dataset.testid || e.target.parentElement.dataset.testid;
  return testId === 'ExpandMoreIcon' || testId === 'ChevronRightIcon';
}

const useStyles = makeStyles(() => ({
  root: {
    flexGrow: 1,
    textAlign: 'left',
  },
  checkbox: {
    padding: '2px !important',
  },
  relationshipElement: {
    color: 'rgba(0, 0, 0, 0.26)',
    fontSize: 15,
  },
  colorMarker: {
    minWidth: '12px',
    minHeight: '12px',
    borderRadius: '50%',
  },
}));

function TreeItems({
  classifiersSearchValue,
  tree,
  code = 'null',
  side,
  checked,
  setChecked,
  setCheckedAll,
  disableSelection,
  disabledElements,
  classifiedElements,
  onDeleteClick,
  isShowClassifierCode,
  isShowVolume,
  elementsFilterValue,
  type,
  idsToColors = {},
}) {
  const classes = useStyles();
  const { isShowColor } = useSelector((state) => state.classifiers);
  return (
    <>
      {tree[code].length > 0 && tree[code].map((item) => {
        const nodeId = item.id;
        const isDisabled = disabledElements.indexOf(item.id) !== -1;
        const relationshipCheckboxClasses = classNames({
          [classes.relationshipElement]: isDisabled,
          [classes.checkbox]: true,
        });
        let classifiedItems;
        const classifiedValues = {
          area: { value: 0, unit: null },
          length: { value: 0, unit: null },
          volume: { value: 0, unit: null },
        };

        const getValuesSumm = (props, key) => ({
          value: classifiedValues[key].value + props[key].value,
          unit: props[key].unit || classifiedValues[key].unit,
        });

        if (classifiedElements[item.id]) {
          classifiedItems = classifiedElements[item.id]
            .map((element) => {
              if (type === 'model') {
                classifiedValues.area = getValuesSumm(element.props, 'area');
                classifiedValues.length = getValuesSumm(element.props, 'length');
                classifiedValues.volume = getValuesSumm(element.props, 'volume');
              }
              return ({ relationshipId: element.id, data: element.data, props: element.props });
            });
        }

        const isBeforeLast = () => {
          let result = true;
          if (tree[item.code] && side === 'left') {
            tree[item.code].forEach((branch) => {
              if (tree[branch.code]) {
                result = false;
              }
            });
          } else {
            result = false;
          }
          return result;
        };

        const isAllDisabled = () => {
          let result = true;
          if (isBeforeLast()) {
            tree[item.code].forEach(({ id }) => {
              if (!(Array.isArray(disabledElements)
                ? disabledElements.indexOf(id) !== -1 : disabledElements === id)) {
                result = false;
              }
            });
          } else {
            result = false;
          }

          return result;
        };

        const isDisabledId = (id) => Boolean(disabledElements
          .find((disabledElement) => disabledElement === id));

        const isInFilesList = (id) => Boolean(tree[item.code].find((file) => file.id === id));

        const getSelectStatus = () => {
          const files = [];

          tree[item.code].forEach(({ id }) => {
            if (!(isDisabledId(id))) {
              files.push(id);
            }
          });

          const selectedFilesLength = checked.filter((id) => isInFilesList(id)).length;
          const filesLength = files.length;

          return {
            checked: filesLength > 0 && selectedFilesLength === filesLength,
            indeterminate: selectedFilesLength > 0 && selectedFilesLength < filesLength,
          };
        };

        const selectAll = (event) => {
          const notDisabledIds = [];

          tree[item.code].forEach(({ id }) => {
            if (!(isDisabledId(id))) {
              notDisabledIds.push(id);
              setCheckedAll(notDisabledIds, event.target.checked);
            }
          });
        };

        return (
          <TreeItem
            key={item.id}
            nodeId={String(nodeId)}
            className={isDisabled && classes.relationshipElement}
            collapseIcon={tree[item.code] || (classifiedItems && elementsFilterValue !== 'unrelated') ? '' : <div />}
            expandIcon={tree[item.code] || (classifiedItems && elementsFilterValue !== 'unrelated') ? '' : <div />}
            label={(
              <Stack
                spacing={1}
                direction="row"
                sx={{ py: '1px' }}
                alignItems="center"
              >
                {isBeforeLast() && !disableSelection && (
                  <Checkbox
                    size="small"
                    indeterminate={getSelectStatus().indeterminate}
                    checked={getSelectStatus().checked}
                    className={relationshipCheckboxClasses}
                    onClick={(e) => e.stopPropagation()}
                    onChange={selectAll}
                    disabled={isAllDisabled()}
                  />
                )}
                {!tree[item.code] && !disableSelection && (
                  <Checkbox
                    size="small"
                    disabled={isDisabled}
                    checked={Array.isArray(checked)
                      ? checked.indexOf(nodeId) !== -1 : checked === nodeId}
                    className={relationshipCheckboxClasses}
                    onClick={(e) => e.stopPropagation()}
                    onChange={(e) => setChecked(nodeId, e.target.checked)}
                  />
                )}
                {(idsToColors[item.id] && classifiedItems && isShowColor) && (
                  <div
                    style={{ backgroundColor: getRgbString(idsToColors[item.id]) }}
                    className={classes.colorMarker}
                  />
                )}
                {isShowClassifierCode && ((type === 'model' && side === 'right') || (type === 'file'))
                  && (
                    <Typography
                      sx={{
                        color: isDisabled ? 'rgba(0,0,0,0.26)' : '#9e9e9e',
                        fontSize: 14,
                      }}
                    >
                      {item.code}
                    </Typography>
                  )}
                <Typography sx={isAllDisabled() && { color: 'rgba(0,0,0,0.26)' }}>
                  {item.description}
                </Typography>
                {isShowVolume && type === 'model' && elementsFilterValue !== 'unrelated' && !classifiersSearchValue
                  && (
                    <Typography style={{
                      fontSize: 12, marginLeft: '8px', whiteSpace: 'nowrap', color: '#0E990E',
                    }}
                    >
                      {`${
                        classifiedValues.volume.value
                          ? classifiedValues.volume.value.toFixed(2)
                          : ''} ${classifiedValues.volume.unit || ''}`}
                    </Typography>
                  )}
              </Stack>
            )}
          >
            {tree[item.code] && (
              <TreeItems
                classifiersSearchValue={classifiersSearchValue}
                tree={tree}
                disabledElements={disabledElements}
                classifiedElements={classifiedElements}
                code={item.code}
                type={type}
                side={side}
                checked={checked}
                setChecked={setChecked}
                setCheckedAll={setCheckedAll}
                isShowClassifierCode={isShowClassifierCode}
                isShowVolume={isShowVolume}
                elementsFilterValue={elementsFilterValue}
                disableSelection={disableSelection}
                onDeleteClick={onDeleteClick}
                idsToColors={idsToColors}
              />
            )}
            {classifiedItems && (elementsFilterValue !== 'unrelated') && (
              classifiedItems.map(({ relationshipId, data, props }) => (
                <>
                  {data && (
                    <TreeItem
                      key={`classified-${data.id}`}
                      nodeId={`classified-${data.id}`}
                      label={(
                        <Stack
                          direction="row"
                          spacing={1}
                          sx={{
                            marginLeft: '16px',
                            marginBottom: '3px',
                          }}
                        >
                          {isShowClassifierCode && type !== 'model'
                            && (
                              <Typography
                                sx={{
                                  fontSize: '14px',
                                  color: (theme) => theme.palette.grey['500'],
                                }}
                              >
                                {data.code}
                              </Typography>
                            )}
                          <Stack direction="row" alignItems="center" spacing={1}>
                            <Typography sx={{ fontSize: '14.3px' }}>
                              {data.description}
                            </Typography>
                            {isShowVolume && type === 'model'
                            && (
                              <span style={{
                                fontSize: 12, marginLeft: '8px', whiteSpace: 'nowrap', color: '#0E990E',
                              }}
                              >
                                {`${props.volume.value ? props.volume.value.toFixed(2) : ''} ${props.volume.unit || ''}`}
                              </span>
                            )}
                            <IconButton
                              size="small"
                              sx={{ p: '1px' }}
                              onClick={() => onDeleteClick(relationshipId)}
                            >
                              <Close sx={{ fontSize: '15px !important' }} />
                            </IconButton>
                          </Stack>
                        </Stack>
                      )}
                    />
                  )}
                </>
              ))
            )}
          </TreeItem>
        );
      })}
    </>
  );
}

export default function TreeViewClassifier(props) {
  const {
    disabledElements,
    classifiersSearchValue,
    classifiers,
    classifiedElements,
    multiSelect = false,
    disableSelection = false,
    onDeleteClick,
    type,
    viewer,
    side,
    selected,
    onSelect = () => {},
    onChecked = () => {},
    idsToColors,
    expanded,
    setExpanded,
    isCollapse,
    setIsCollapse,
    fileToCompare,
  } = props;
  const classes = useStyles();
  const dispatch = useDispatch();
  const initialChecked = multiSelect ? [] : null;
  const [checked, setChecked] = useState(initialChecked);
  const selectedModel = useSelector((state) => state.projects.selectedModel);
  const activeSide = useSelector((state) => state.app.activeClassifyingSide);
  const {
    elementsFilterValue, isShowVolume, isShowClassifierCode,
  } = useSelector((state) => state.classifiers);
  const isAutoClassifying = useSelector((state) => state.app.isNeedToCreateRelationships);
  let classifiersTree = _.groupBy(classifiers, (c) => c.parent_code);

  const expandedSearchedElements = [];
  const searchedElements = [];
  const expandedBeforeSearch = useMemo(() => !classifiersSearchValue && expanded, [fileToCompare]);

  useEffect(() => {
    onChecked(checked);
  }, [checked]);

  function filterTree(tree, key) {
    const currentElements = tree[key];

    return currentElements.filter((obj) => {
      const nestedTree = tree[obj.code];

      let isRemove = false;
      if (nestedTree) {
        const filteredTree = filterTree(tree, obj.code);
        isRemove = filteredTree.length === 0;
      } else if (elementsFilterValue === 'related') {
        if (side === 'left') isRemove = !disabledElements.includes(obj.id);
        else isRemove = !Object.keys(classifiedElements).includes(String(obj.id));
      } else if (elementsFilterValue === 'unrelated') {
        if (side === 'left') isRemove = disabledElements.includes(obj.id);
        else isRemove = false;
      }
      return !isRemove;
    });
  }

  function collapseTree(tree, key) {
    const currentElements = tree[key];

    return currentElements.filter((obj) => {
      const nestedTree = tree[obj.code];

      let isRemove;
      if (nestedTree) {
        const collapsedTree = collapseTree(tree, obj.code);
        isRemove = collapsedTree.length === 0;
      } else {
        isRemove = !Object.keys(classifiedElements).includes(String(obj.id));
      }
      return !isRemove;
    });
  }

  function searchTree(tree, key) {
    const currentElements = tree[key];

    function pushExpandedSearchedElements(classifier) {
      if (!expandedSearchedElements
        .find((expandedElement) => expandedElement.id === classifier.id)) {
        expandedSearchedElements.push(classifier);
      }
      const expandedIdsArray = expandedSearchedElements
        .map((expandedElement) => expandedElement.id.toString());
      setExpanded(expandedIdsArray);
    }

    return currentElements.filter((obj) => {
      const nestedTree = tree[obj.code];

      let isRemove = false;
      let isHaveTypeSize = false;

      if (classifiersSearchValue) {
        const isFindSearchValue = (objectDescription) => objectDescription.toLowerCase()
          .includes(classifiersSearchValue.toLowerCase());

        if (nestedTree) {
          const searchedTree = searchTree(tree, obj.code);

          if (obj.parent_code === null && !isFindSearchValue(obj.description)) {
            pushExpandedSearchedElements(obj);
          }

          isRemove = searchedTree.length === 0;

          if (searchedTree.length > 0
            && searchedTree
              .some((innerClassifier) => isFindSearchValue(innerClassifier.description))) {
            pushExpandedSearchedElements(obj);
          }

          if (isFindSearchValue(obj.description)) {
            searchedElements.push(obj);
            tree[obj.code].forEach((treeElement) => searchedElements.push(treeElement));

            isRemove = false;
          } else {
            searchedElements.push(obj);

            tree.null.forEach((topClassifier) => {
              if (obj.parent_code === topClassifier.code) {
                searchedElements.push(obj);
                const innerClassifiers = tree[topClassifier.code];
                const finiteClassifiers = tree[obj.code];

                isRemove = searchedTree.length === 0;

                const isSearchedTopClassifier = isFindSearchValue(topClassifier.description)
                  && !innerClassifiers
                    .some((innerClassifier) => isFindSearchValue(innerClassifier.description))
                  && !finiteClassifiers
                    .some((finiteClassifier) => isFindSearchValue(finiteClassifier.description));

                const isSearchedFiniteClassifier = !isFindSearchValue(topClassifier.description)
                  && !innerClassifiers
                    .some((innerClassifier) => isFindSearchValue(innerClassifier.description))
                  && finiteClassifiers
                    .some((finiteClassifier) => isFindSearchValue(finiteClassifier.description));

                isRemove = !isSearchedTopClassifier
                  && !searchedTree
                    .some((innerClassifier) => isFindSearchValue(innerClassifier.description));

                if (isSearchedFiniteClassifier) {
                  isRemove = false;
                }

                Object.values(classifiedElements).forEach((classifiedElement) => {
                  classifiedElement.forEach((typeSizeElement) => {
                    finiteClassifiers.forEach((innerClassifier) => {
                      const isClassifierHaveSearchedTypeSize = (innerClassifier.id === (type === 'model' ? typeSizeElement.classifier_id : typeSizeElement.classifier_id1))
                        || (innerClassifier.id === (type === 'model' ? typeSizeElement.classifier_id : typeSizeElement.classifier_id2));
                      if (isClassifierHaveSearchedTypeSize) {
                        isRemove = false;
                      } else if (searchedTree.length === 0) {
                        isRemove = true;
                      }
                    });
                  });
                });
              }
            });
          }
        }
        if (!nestedTree) {
          const innerClassifiers = tree[obj.parent_code];

          if (classifiedElements) {
            Object.values(classifiedElements).forEach((classifiedElement) => {
              classifiedElement.forEach((element) => {
                if (((obj.id === (type === 'model' ? element.classifier_id : element.classifier_id2))
                  || (obj.id === (type === 'model' ? element.classifier_id : element.classifier_id1)))
                  && isFindSearchValue(element.data.description)) {
                  isHaveTypeSize = true;
                  isRemove = false;
                  setIsCollapse(false);
                  pushExpandedSearchedElements(obj);
                }
              });
            });
          }

          isRemove = !isFindSearchValue(obj.description) && !isHaveTypeSize;

          searchedElements.forEach((searchedElement) => {
            if (searchedElement.code === obj.parent_code) {
              searchedElements.push(obj);

              if (obj.parent_code === searchedElement.code
                && isFindSearchValue(searchedElement.description)) {
                isRemove = false;
              }

              const topClassifier = tree.null
                .filter((treeElement) => treeElement.code === searchedElement.parent_code);

              if (!topClassifier.some((classifier) => isFindSearchValue(classifier.description))
                && isFindSearchValue(searchedElement.description)
                && !innerClassifiers
                  .some((innerClassifier) => isFindSearchValue(innerClassifier.description))) {
                isRemove = false;
              }

              const allInFirstLevel = topClassifier
                .some((classifier) => isFindSearchValue(classifier.description))
                && !innerClassifiers
                  .some((innerClassifier) => isFindSearchValue(innerClassifier.description))
                && !isFindSearchValue(searchedElement.description);

              if (allInFirstLevel) {
                isRemove = false;
              }
            }
          });
        }
      }
      if (!classifiersSearchValue) {
        setExpanded(expandedBeforeSearch);
        isRemove = false;
      }
      return !isRemove;
    });
  }

  function getFilteredTree(tree) {
    const treeKeys = Object.keys(tree);
    const newTree = {};

    for (let i = 0; i < treeKeys.length; i += 1) {
      newTree[treeKeys[i]] = filterTree(tree, treeKeys[i]);
    }

    return newTree;
  }

  function getCollapsedTree(tree) {
    const treeKeys = Object.keys(tree);
    const collapsedTree = {};

    for (let i = 0; i < treeKeys.length; i += 1) {
      collapsedTree[treeKeys[i]] = collapseTree(tree, treeKeys[i]);
    }
    return collapsedTree;
  }

  function getSearchedTree(tree) {
    const treeKeys = Object.keys(tree);
    const searchedTree = {};

    for (let i = 0; i < treeKeys.length; i += 1) {
      searchedTree[treeKeys[i]] = searchTree(tree, treeKeys[i]);
    }

    return searchedTree;
  }

  classifiersTree = useMemo(
    () => {
      let newTree = getFilteredTree(classifiersTree);
      newTree = getSearchedTree(newTree);
      if (elementsFilterValue !== 'all' && side === 'left' && viewer) {
        const idsByFiles = {};
        Object.values(newTree)
          .forEach((elements) => {
            elements.forEach((element) => {
              const fileIds = Object.keys(element.data.idsByFilesId);
              const elementsIds = Object.values(element.data.idsByFilesId);

              fileIds.forEach((fileId, i) => {
                const modelUrn = selectedModel.urns
                  .find(({ id: modelId }) => fileId === modelId).urn;
                if (idsByFiles[modelUrn]) {
                  idsByFiles[modelUrn] = [
                    ...idsByFiles[modelUrn],
                    ...elementsIds[i],
                  ];
                } else {
                  idsByFiles[modelUrn] = elementsIds[i];
                }
              });
            });
          });
        isolateElements(idsByFiles, viewer);
      } else if (viewer) {
        isolateElements([], viewer);
      }
      return newTree;
    },
    [classifiers,
      disabledElements,
      elementsFilterValue,
      classifiersSearchValue],
  );

  useEffect(() => {
    setChecked(initialChecked);
    if (classifiedElements && Object.keys(classifiedElements).length === 0
      && fileToCompare && fileToCompare.type === 'project') {
      setExpanded([]);
    }
    if (classifiedElements && isAutoClassifying) {
      const newExpanded = Object.keys(classifiedElements);
      setExpanded(newExpanded.concat(expanded));
      setIsCollapse(false);
    }
    if (checked && checked.length !== 0) {
      setExpanded(expanded.concat(checked.toString()));
    }
  }, [disabledElements, classifiedElements, isAutoClassifying]);

  useEffect(() => {
    if (classifiedElements && classifiedElements.length !== 0
      && fileToCompare
      && isCollapse === false) {
      const collapsedTree = getCollapsedTree(classifiersTree);
      const arrClassifiersTree = Object.values(collapsedTree);
      const arrClassifiedElements = Object.keys(classifiedElements);

      const classifiedFolders = [];
      arrClassifiersTree.forEach((classifier) => {
        if (classifier.length > 0) {
          const arrClassifier = Object.values(classifier);
          arrClassifier.forEach(({ id }) => {
            const isElementIdClassified = arrClassifiedElements
              .find((elementId) => elementId === String(id));
            if (!isElementIdClassified) classifiedFolders.push(String(id));
          });
        }
      });
      setExpanded(classifiedFolders.concat(expanded));
      setIsCollapse(true);
    }
  }, [fileToCompare, classifiedElements, isAutoClassifying, isCollapse]);

  if (!classifiers) {
    return <CircularProgress />;
  }

  if (classifiers.length === 0) {
    return 'Нет информации';
  }

  if (classifiersTree.null.length === 0) {
    if (classifiersSearchValue) {
      return (
        <Alert severity="info" sx={{ margin: 0.5 }}>
          {`Не найдены классификаторы по поисковому запросу: "${classifiersSearchValue}"`}
        </Alert>
      );
    }
    if (elementsFilterValue === 'related') {
      return (
        <Alert severity="info" sx={{ margin: 0.5 }}>
          {type === 'file' || (type === 'model' && side === 'right')
            ? 'Нет сопоставленных классификаторов'
            : ''}
          {(type === 'model' && side === 'left')
          && 'Нет сопоставленных элементов'}
        </Alert>
      );
    }
    if (elementsFilterValue === 'unrelated') {
      return (
        <Alert severity="info" sx={{ margin: 0.5 }}>
          {type === 'file' && 'Все классификаторы сопоставлены'}
          {type === 'model' && 'Все элементы сопоставлены'}
        </Alert>
      );
    }
  }

  const selectActiveSide = () => {
    if (activeSide !== side && type === 'model') {
      dispatch(setActiveClassifyingSide(side));
    }
  };

  const onNodeSelect = (e, ids) => {
    if (!isEventFromIcon(e)) {
      onSelect(ids);
    }
    selectActiveSide();
  };

  return (
    <TreeView
      selected={selected}
      onNodeSelect={onNodeSelect}
      onClick={selectActiveSide}
      className={classes.root}
      defaultCollapseIcon={<ExpandMore />}
      defaultExpandIcon={<ChevronRight />}
      multiSelect
      disableSelection={disableSelection}
      expanded={expanded}
      onNodeToggle={(e, ids) => {
        if (isEventFromIcon(e)) {
          setExpanded(ids);
        }
        e.stopPropagation();
      }}
    >
      <TreeItems
        classifiersSearchValue={classifiersSearchValue}
        tree={classifiersTree}
        idsToColors={idsToColors}
        disabledElements={disabledElements || []}
        classifiedElements={classifiedElements || {}}
        disableSelection={disableSelection}
        onDeleteClick={onDeleteClick}
        type={type}
        isShowClassifierCode={isShowClassifierCode}
        isShowVolume={isShowVolume}
        elementsFilterValue={elementsFilterValue}
        side={side}
        checked={checked}
        setChecked={(id, isChecked) => {
          let newSelected;
          if (multiSelect) {
            newSelected = isChecked
              ? [...checked, id]
              : checked.filter((item) => item !== id);
          } else {
            newSelected = isChecked ? id : null;
          }
          setChecked(newSelected);
          selectActiveSide();
        }}
        setCheckedAll={(ids, isChecked) => {
          const newSelected = isChecked
            ? [...checked, ...ids]
            : checked.filter((item) => ids.indexOf(item) === -1);
          setChecked(newSelected);

          const uniqueNewSelected = newSelected
            .filter((element, index) => newSelected.indexOf(element) === index);
          setChecked(uniqueNewSelected);
          selectActiveSide();
        }}
      />
    </TreeView>
  );
}
